from pinecone import Pinecone, ServerlessSpec
from collections import Counter  # 用于计数
from tqdm import tqdm  # 用于显示进度条
# 从 scikit-learn 库中导入 load_digits 函数
# 这个函数用于加载著名的手写数字数据集 MNIST
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
import time

pinecone = Pinecone(api_key="fa0e09bb-8f42-4a07-8c35-93ea8ecb5e2b")

# 索引名称
index_name = "mnist-index"

# 获取现有索引列表
existing_indexes = pinecone.list_indexes()

# 检查索引是否存在，如果存在就删除
# 这个if是否需要，要看情况而定
# 比如有的时候，如果不想要重复删除在创建，这个if就可以不要
if any(index['name'] == index_name for index in existing_indexes):
    print(f"索引 '{index_name}' 已存在，将删除索引重新创建。")
    pinecone.delete_index(index_name)
    print(f"索引 '{index_name}' 已成功删除。")
else:
    print(f"索引 '{index_name}' 不存在，将创建新索引。")
# 创建新索引
print(f"正在创建新索引 '{index_name}'...")
pinecone.create_index(
    name=index_name,
    dimension=64,  # MNIST 每个图像展平后是一个 64 维向量
    metric="euclidean",  # 使用欧氏距离
    spec=ServerlessSpec(
        cloud="aws",
        region="us-east-1"
    )
)
print(f"索引 '{index_name}' 创建成功。")

# 连接到索引
index = pinecone.Index(index_name)
print(f"已成功连接到索引 '{index_name}'。")


# 使用 load_digits 函数加载 MNIST 数据集
# n_class=10 表示加载全部 10 个数字类别(0-9)
digits = load_digits(n_class=10)

# 获取数据集中的特征数据
# X 是一个二维数组,每行代表一个样本,每个样本是一个 64 维的向量(8x8 像素展平)
X = digits.data

# 获取数据集中的标签
# y 是一个一维数组,包含每个样本对应的真实数字标签(0-9)
y = digits.target

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 初始化一个空列表,用于存储转换后的向量数据
vectors = []

# 遍历所有样本,将数据转换为 Pinecone 可接受的格式
for i in range(int(len(X_train))):
    # 使用样本的索引作为向量的唯一标识符
    vector_id = str(i)
    
    # 将 NumPy 数组转换为 Python 列表
    # Pinecone 要求输入数据为 Python 列表格式
    vector_values = X_train[i].tolist()
    
    # 创建元数据字典,包含该样本的真实标签
    # 将标签转换为整数类型,确保数据类型的一致性
    metadata = {"label": int(y_train[i])}
    
    # 将转换后的数据(ID、向量值、元数据)作为元组添加到 vectors 列表中
    vectors.append((vector_id, vector_values, metadata))

# 定义批处理大小,每批最多包含 1000 个向量
# 这是为了避免一次性向 Pinecone 发送过多数据,可能导致请求超时或失败
batch_size = 1000


# 使用步长为 batch_size 的 range 函数,实现分批处理
for i in tqdm(range(0, len(vectors), batch_size)):
    # 从 vectors 列表中切片获取一批数据
    batch = vectors[i:i + batch_size]
    
    # 使用 upsert 方法将这批数据上传到 Pinecone 索引中
    # upsert 操作会插入新的向量或更新已存在的向量
    index.upsert(batch)
print(f"成功上传了 {len(vectors)} 个向量到Pinecone中的索引{index_name}。")
# 用Pinecone索引搜索最近的向量,并返回它们的ID,测试准确率
# 初始化一个空列表,用于存储搜索结果
final_prediction = []
time.sleep(5)  # 等待 5 秒以确保索引已经准备好
for i in tqdm(range(len(X_test))):
    # 从测试集中获取一个样本,并将其转换为 Python 列表
    query_data = X_test[i].tolist()
    results = index.query(
        vector=query_data,
        top_k=11,  # 返回距离最近的 11 个结果
        include_metadata=True  # 同时返回每个向量的元数据(包括标签)
    )
    # 从搜索结果中提取每个匹配项的标签
    labels = [match['metadata']['label'] for match in results['matches']]
    # 使用投票机制确定最终的分类结果
    # Counter().most_common(1) 返回出现次数最多的元素
    # [0][0] 获取该元素的值(即预测的数字)
    final_prediction.append(Counter(labels).most_common(1)[0][0])
# 计算测试集的准确率
accuracy = sum([1 for i in range(len(y_test)) if y_test[i] == final_prediction[i]]) / len(y_test)
print(f"k = 11 时的测试集准确率为:   {accuracy:.4f}")
 